# forward-compat boilerplate
from __future__ import absolute_import
from __future__ import with_statement
__metaclass__ = type

from fenton import util
from fenton import types
from fenton import getmeta
from fenton import security


EMPTY = (None, [], '', {})


# called only by web.Request.vars
# and therefore created only by form inputs and URLs
def decode(indict, dch='.', lch='-'):
    result = {}
    sort_keys = set()
    for key, value in indict.iteritems():
        keys = key.split(dch)
        new_keys = []
        for key in keys:
            if lch in key:
                key, index = key.split(lch, 1)
                try:
                    index = int(index)
                except ValueError:
                    continue
                new_keys.append(key)
                sort_keys.add(tuple(new_keys))
                new_keys.append(index)
            else:
                new_keys.append(key)

        if not new_keys:
            continue

        place = result
        for i in range(len(new_keys)-1):
            try:
                if not isinstance(place[new_keys[i]], dict):
                    place[new_keys[i]] = {None: place[new_keys[i]]}
                place = place[new_keys[i]]
            except KeyError:
                place[new_keys[i]] = {}
                place = place[new_keys[i]]
        if new_keys[-1] in place:
            if isinstance(place[new_keys[-1]], dict):
                place[new_keys[-1]][None] = value
            elif isinstance(place[new_keys[-1]], list):
                if isinstance(value, list):
                    place[new_keys[-1]].extend(value)
                else:
                    place[new_keys[-1]].append(value)
            else:
                if isinstance(value, list):
                    place[new_keys[-1]] = [place[new_keys[-1]]]
                    place[new_keys[-1]].extend(value)
                else:
                    place[new_keys[-1]] = [place[new_keys[-1]], value]
        else:
            place[new_keys[-1]] = value

    keys = sorted(sort_keys, cmp=lambda a,b:-cmp(len(a),len(b)))
    for key in keys:
        to_sort = result
        source = None
        last_key = None
        for sub_key in key:
            source = to_sort
            last_key = sub_key
            to_sort = to_sort[sub_key]
        if None in to_sort:
            noneVals = [(0, x) for x in to_sort[None]]
            del to_sort[None]
            noneVals.extend(to_sort.items())
            to_sort = noneVals
        else:
            to_sort = to_sort.items()
        to_sort.sort()
        to_sort = [v for k, v in to_sort]
        source[last_key] = to_sort

    return result


# called only by view.get_url() (view.py and system.py)
def encode(inval, prefix='', dch='.', lch='-', outval=None):
    if outval is None:
        outval = {}
    if isinstance(inval, dict):
        for key, value in inval.items():
            if key is None:
                name = prefix
            elif not prefix:
                name = key
            else:
                name = '%s%s%s' % (prefix, dch, key)
            encode(value, name, dch, lch, outval)
    elif isinstance(inval, list):
        for i in range(len(inval)):
            prefix = '%s%s%i' % (prefix, lch, i)
            encode(inval[i], prefix, dch, lch, outval)
    else:
        outval[prefix] = inval
    return outval




class RequiredValueMissing(types.Invalid):
    message = 'Required value missing'



class Form:

    def __init__(self, context, model, editable, errors):
        self.context = context
        self.model = model
        self.editable = editable
        self.errors = errors
        self.pushback_keys = []
        self.meta = getmeta(model)
        self.groups = self._groups()

        from itertools import chain
        fs = chain(*[fs for (k, h, fs) in self.groups])
        self.fields = dict((f.key, f) for f in fs)

        key = self.meta.key
        self.keys = [k for (k, f) in self.fields.iteritems()
                        if f.editable
                            and f.type.get_validator()
                            and k not in key]



    @util.yieldlist
    def _fields(self, keys, offset):
        i = 0
        meta = getmeta(self.model)
        for k in keys:
            type = meta.attributes[k]
            if self.editable:
                check = type.showedit
            else:
                check = type.showview
            if security.check(check, self.context, self.model):
                editable = self.editable and security.check(type.editable,
                                                            self.context,
                                                            self.model)

                required = self.editable and security.check(type.required,
                                                       self.context,
                                                       self.model)

                yield Field(self, i+offset, type, editable, required)
                i += 1


    @util.yieldlist
    def _groups(self):
        count = 0
        for groupkey, header, keys in self.meta.fields:
            fields = self._fields(keys, count)
            if fields:
                count += len(fields)
                yield groupkey, header, fields


    def get_args(self):
        return dict((k, self.fields[k].get_simplified())
                    for k in self.keys)


    def pushback(self, *keys):
        for key in keys:
            self.errors[key] = None
            self.pushback_keys.append(key)


    def get_pushback(self):
        if self.pushback_keys:
            return dict((k, self.fields[k].get_string())
                        for k in self.pushback_keys)


    # called only by view.update()
    def validate(self, incoming, only=None):
        orig = {}
        reconstructed = {}

        meta = self.meta

        # first loop: formal validation: str -> py
        for k in only or self.fields.keys():
            field = self.fields.get(k)
            if not field or not field.editable:
                continue
            orig[k] = field.get_value()
            self.errors[k] = None
            v = field.type.get_validator()
            if not v:
                reconstructed[k] = orig[k]
                continue
            value = incoming.get(k, '')
            try:
                reconstructed[k] = v.reconstruct(value, self.context)
            except types.Invalid, e:
                self.errors[k] = e
                if e.input is None:
                    e.input = value


        # second loop: semantic validation
        keys = reconstructed.keys()
        missing = []
        while keys:
            key = keys.pop(0)
            #print 'form.update:', key
            field = self.fields[key]
            value = reconstructed.get(key)
            if self.errors.get(key):
                continue
            if field.required and value in EMPTY:
                self.errors[key] = RequiredValueMissing(input=value)
                missing.append(key)
                continue
            try:
                meta.update_model(self.context, self.model, {key: value}, self)
            except types.Invalid, e:
                self.errors[key] = e
                if e.input is None:
                    e.input = incoming.get(key)
            else:
                self.errors[key] = None




class Field:

    name = util.lazyattr(lambda x:x.request.model_key + '.' + x.key)
    request = property(lambda x:x.context.request)
    context = property(lambda x:x.form.context)
    error = property(lambda x:x.form.errors.get(x.key))

    label = property(lambda x:x.type.get_label())
    hint = property(lambda x:x.type.get_hint())

    def __init__(self, form, index, type, editable, required):
        self.form = form
        self.index = index
        self.type = type
        self.key = type.key
        self.editable = editable
        self.required = required

    def get_value(self):
        return self.type.get_value(self.context, self.form.model, self.key)

    def get_simplified(self):
        e = self.form.errors.get(self.key)
        if e is not None:
            return e.input
        v = self.type.get_validator()
        return v.simplify(self.get_value(), self.context)

    def get_formatted(self):
        return self.type.format_value(self.get_value(), self.context)

    def get_string(self):
        return (self.editable and self.get_simplified or self.get_formatted)()

    @util.lazyattr
    def widget(self):
        return self.type.get_widget(self)




